package org.statewalker.graph;

/**
 * This class is used to walk over graph structures.
 * 
 * @see INodeWalkerSource
 * @see INodeWalkerListener
 * @author mkotelnikov
 */

public abstract class AbstractNodeWalker<T, E extends Throwable> {

    /**
     * Possible modes of traversal of a graph; it defines when the iterator
     * returns the control to the caller - after the entering in a node, after
     * exiting from a node, for leaf nodes or for each step.
     * 
     * @see NodeWalkerIterator#getMode()
     */
    public enum Mode {

        /**
         * Control is returned when the walker enters in a node
         */
        IN(1),

        /**
         * 
         */
        NEW(4),

        /**
         * Controls is returned when the walker goes out of a node
         */
        OUT(8),

        /**
         * 
         */
        TERMINTATED(16);

        public static int mask(Mode... values) {
            int mask = 0;
            for (Mode mode : values) {
                mask |= mode.fValue;
            }
            return mask;
        }

        private int fValue;

        Mode(int value) {
            fValue = value;
        }

        public boolean check(int mask) {
            return (mask & fValue) != 0;
        }

        public boolean check(Mode mode) {
            return mode == this;
        }

    }

    // private T fFirstNode;

    /**
     * The source of nodes in the graph
     */
    protected INodeWalkerSource<T, E> fSource;

    protected Mode fStatus = Mode.TERMINTATED;

    protected boolean fVisit = true;

    /**
     * @param source
     * @throws E
     */
    public AbstractNodeWalker(INodeWalkerSource<T, E> source, T topNode) {
        fSource = source;
        reset(topNode);
    }

    protected abstract void clearNodeStack();

    /**
     * @return the current active node
     * @throws E
     */
    public T getCurrentNode() {
        return getPeekNode();
    }

    /**
     * Returns the topmost node in the stack withot removing it from the stack
     * throws
     * 
     * @return the topmost node in the stack
     */
    protected abstract T getPeekNode();

    /**
     * Returns the current status of the walker
     * 
     * @return the current status of the walker
     */
    public Mode getStatus() {
        return fStatus;
    }

    /**
     * Returns <code>true</code> if the walking process is finished and the
     * walker can not make the next step. This method returns <code>true</code>
     * if the both methods {@link #getPeekNode()} and {@link #getNextNode()}
     * return <code>null</code>.
     * 
     * @return <code>true</code> if this walker can not make the next step
     */
    public boolean isFinished() {
        return fStatus == Mode.TERMINTATED;
    }

    /**
     * Removes the topmost node of the stack and returns it.
     * 
     * @return the removed topmost node of the stack
     */
    protected abstract T popNode();

    /**
     * Pushes the given node in the top of the stack
     * 
     * @param currentNode
     */
    protected abstract void pushNode(T currentNode);

    /**
     * Resets the stack and sets the given node as the next node to visit
     * 
     * @param root a new root node of the tree to visit
     */
    public void reset(T root) {
        clearNodeStack();
        if (root != null) {
            pushNode(root);
            fStatus = Mode.NEW;
        } else {
            fStatus = Mode.TERMINTATED;
        }
    }

    /**
     * Loads the next node in the graph structure. Each step enters in a node or
     * exit from current active node.
     * 
     * @param source this is a source of graph nodes
     * @param context this context contains dynamic (run-time) information about
     *        the current position of the walker in the graph structure
     * @param nodeListener this listener is used to notify about events occurred
     *        with each node: walker goes in the node or goes out from it, when
     *        walker starts to traverse subnodes of a node or when it finish
     *        subnode traversing.
     * @return <code>true</code> if the walker goes down in the tree branches
     *         and <code>false</code> if it goes out of a node
     */
    public Mode shift(INodeWalkerListener<T, E> nodeListener) throws E {
        T next = null;
        switch (fStatus) {
            case TERMINTATED:
                return fStatus;
            case NEW:
                next = popNode();
                break;
            case IN: {
                T peek = getPeekNode();
                next = fVisit ? fSource.getFirstSubnode(peek) : null;
                break;
            }
            case OUT: {
                T node = popNode();
                T peek = getPeekNode();
                nodeListener.endNode(peek, node);
                next = fSource.getNextSubnode(peek, node);
                break;
            }
        }

        if (next != null) {
            T peek = getPeekNode();
            fVisit = nodeListener.beginNode(peek, next);
            pushNode(next);
            fStatus = Mode.IN;
        } else {
            fStatus = getPeekNode() != null ? Mode.OUT : Mode.TERMINTATED;
        }
        return fStatus;
    }

    public Mode shift1(INodeWalkerListener<T, E> nodeListener) throws E {
        T next;
        if (fStatus == Mode.IN || fStatus == Mode.NEW) {
            T peek = getPeekNode();
            next = fVisit ? fSource.getFirstSubnode(peek) : null;
        } else {
            T node = popNode();
            T peek = getPeekNode();
            nodeListener.endNode(peek, node);
            next = fSource.getNextSubnode(peek, node);
        }

        if (next != null) {
            T peek = getPeekNode();
            fVisit = nodeListener.beginNode(peek, next);
            pushNode(next);
            fStatus = Mode.IN;
        } else {
            fStatus = getPeekNode() != null ? Mode.OUT : Mode.TERMINTATED;
        }
        return fStatus;
    }

}